`include "define.vh"


/**
 * Parallel-Serial Converter.
 * Author: Zhao, Hongyu  <power_zhy@foxmail.com>
 */
module parallel2serial (
	input wire clk,  // main clock
	input wire rst,  // synchronous reset
	input wire [DATA_BITS-1:0] data,  // parallel input data
	input wire start,  // start command
	output reg busy,  // busy flag
	output reg finish,  // finish acknowledgement
	output reg s_clk = 0,  // serial clock
	output reg s_clr = 0,  // serial clear
	output wire s_dat  // serial output data
	);
	
	`include "function.vh"
	parameter
		P_CLK_FREQ = 100,  // main clock frequency in MHz
		S_CLK_FREQ = 10,  // serial clock frequency in MHz
		DATA_BITS = 32,  // data length
		CODE_ENDIAN = 0;  // 0 for little-endian and 1 for big-endian
	localparam
		COUNT_HALFCYCLE = 1 + (P_CLK_FREQ-1) / S_CLK_FREQ / 2,
		CYCLE_COUNT_BITS = GET_WIDTH(COUNT_HALFCYCLE-1),
		DATA_COUNT_BITS = GET_WIDTH(DATA_BITS-1);
	
	reg [DATA_BITS:0] buff;
	reg [CYCLE_COUNT_BITS-1:0] cycle_count = 0;
	wire s_clk_negedge;
	
	localparam
		S_IDLE = 0,  // idle
		S_CLEAR = 1,  // clear
		S_WORK = 2,  // converting
		S_DONE = 3;  // acknowledge
	
	reg [2:0] state = 0;
	reg [2:0] next_state;
	reg [DATA_COUNT_BITS-1:0] data_count = 0;
	reg [DATA_COUNT_BITS-1:0] next_data_count;
	
	always @(*) begin
		next_state = 0;
		next_data_count = 0;
		case (state)
			S_IDLE: begin
				if (start) begin
					next_state = S_CLEAR;
				end
				else begin
					next_state = S_IDLE;
				end
			end
			S_CLEAR: begin
				if (s_clk_negedge) begin
					next_state = S_WORK;
				end
				else begin
					next_state = S_CLEAR;
				end
			end
			S_WORK: begin
				if (s_clk_negedge) begin
					next_data_count = data_count + 1'h1;
					if (data_count == DATA_BITS-1)
						next_state = S_DONE;
					else
						next_state = S_WORK;
				end
				else begin
					next_data_count = data_count;
					next_state = S_WORK;
				end
			end
			S_DONE: begin
				next_state = S_IDLE;
			end
		endcase
	end
	
	always @(posedge clk) begin
		if (rst) begin
			state <= 0;
			data_count <= 0;
		end
		else begin
			state <= next_state;
			data_count <= next_data_count;
		end
	end
	
	always @(posedge clk) begin
		busy <= 0;
		finish <= 0;
		s_clr <= 0;
		if (~rst) case (next_state)
			S_IDLE: begin
				buff <= 0;
			end
			S_CLEAR: begin
				busy <= 1;
				if (CODE_ENDIAN)
					buff <= {1'b0, data};
				else
					buff <= {data, 1'b0};
				s_clr <= 1;
			end
			S_WORK: begin
				busy <= 1;
				if (s_clk_negedge) begin
					if (CODE_ENDIAN)
						buff <= {buff[DATA_BITS-1:0], 1'b0};
					else
						buff <= {1'b0, buff[DATA_BITS:1]};
				end
			end
			S_DONE: begin
				finish <= 1;
			end
		endcase
	end
	
	assign s_dat = CODE_ENDIAN ? buff[DATA_BITS] : buff[0];
	
	always @(posedge clk) begin
		if (rst || state==S_IDLE || state==S_DONE) begin
			cycle_count <= 0;
			s_clk <= 0;
		end
		else begin
			if (cycle_count == COUNT_HALFCYCLE-1) begin
				cycle_count <= 0;
				s_clk <= ~s_clk;
			end
			else begin
				cycle_count <= cycle_count + 1'h1;
			end
		end
	end
	
	assign
		s_clk_negedge = (cycle_count == COUNT_HALFCYCLE-1) && s_clk;  // make it one clock prior than s_clk so that s_dat can be synchronized with s_clk
	
endmodule
